#include "MySQLDao.h"
#include <chrono>
#include "ConfigMgr.h"
#include "Logger.hpp"

MySQLConnectionPool::MySQLConnectionPool(std::string url, std::string username, std::string pwd, std::string schema, int pool_size)
    : _url(url), _username(username), _pwd(pwd), _schema(schema), _pool_size(pool_size)
{
    try
    {
        // 为连接池添加连接对象
        for (int i = 0; i < _pool_size; i++)
        {
            sql::mysql::MySQL_Driver *driver = sql::mysql::get_mysql_driver_instance();
            // 获取当前时间
            auto timestamp = std::chrono::system_clock::now().time_since_epoch();
            long long currTime = std::chrono::duration_cast<std::chrono::seconds>(timestamp).count();
            // 从数据库获取一个连接，并构造一个新的SqlConnection连接，然后放到连接池中
            auto *conn = driver->connect(_url, _username, _pwd);
            conn->setSchema(_schema);
            _pool.push(std::make_unique<SqlConnection>(conn, currTime));
        }

        _check_thread = std::thread([&]()
                                    {
            while(!_b_stop)
            {
                checkConnection();      //执行检查
                std::this_thread::sleep_for(std::chrono::seconds(60));  //让检查线程休眠60s
            } });

        _check_thread.detach();
    }
    catch (std::exception e)
    {
        LOG_DB_ERROR("MySQL init failed! Exception is: ->{}", e.what());
    }
    // catch (sql::SQLException e)
    // {
    //     LOG_DB_ERROR("MySQL init failed! Exception is: ->{}", e.what());
    // }
}

MySQLConnectionPool::~MySQLConnectionPool()
{
    std::unique_lock<std::mutex> lock(_mutex);
    _b_stop = true;
    while (!_pool.empty())
    {
        _pool.pop();
    }
}

void MySQLConnectionPool::checkConnection()
{
    // 获取当前时间
    auto timestamp = std::chrono::system_clock::now().time_since_epoch();
    long long currTime = std::chrono::duration_cast<std::chrono::seconds>(timestamp).count();

    std::lock_guard<std::mutex> lock(_mutex);
    try
    {
        // 对每个连接进行检查
        for (int i = 0; i < _pool_size; i++)
        {
            auto con(std::move(_pool.front()));
            _pool.pop();
            Defer def( // 以免忘记回收连接
                [&]()
                { _pool.push(std::move(con)); });
            if (currTime - con->_last_oper_time < 5)
            {
                continue;
            }

            auto stmt = con->_con->createStatement();
            stmt->executeQuery("SELECT 1"); // 通过尝试查询来检查该连接是否有效
            con->_last_oper_time = currTime;
            // _pool.push(std::move(con));
        }
    }
    catch (std::exception e)
    {
        LOG_DB_ERROR("MySQL checking failed! Exception is: {}", e.what());
    }
    catch (sql::SQLException &e) // 当查询出错时，可以在这里进行连接的替换
    {
        LOG_DB_ERROR("MySQL checking failed! Exception is: ->{} replacing connection...", e.what());
        // 替换连接
        try
        {
            sql::mysql::MySQL_Driver *driver = sql::mysql::get_mysql_driver_instance();
            auto con = driver->connect(_url, _username, _pwd);
            con->setSchema(_schema);
            // _pool.push(std::make_unique<SqlConnection>(con, currTime));
            _pool.back()->_con.reset(con); // 因为前面使用RAII自动添加了连接，所以这里只需要更改con指针和更新操作时间即可
            _pool.back()->_last_oper_time = currTime;
        }
        catch (sql::SQLException &e)
        {
            LOG_DB_ERROR("MySQL replacing connection failed! Exception is: {}", e.what());
            // 若创建还是失败，则将连接个数减1
            _pool_size--;
        }
    }
}

std::unique_ptr<SqlConnection> MySQLConnectionPool::getConnection()
{
    std::unique_ptr<SqlConnection> conn;
    std::unique_lock<std::mutex> lock(_mutex);
    _cond.wait(lock, [this]()
               {
        if (_b_stop)    //若连接池已关闭，则只能返回空指针，不能再返回任何连接
            return true;
        return !_pool.empty(); });

    if (_b_stop)
    {
        // std::cout << "MySQL connection pool is stop, it will reutrn nullptr!" << std::endl;
        LOG_DB_WARN("MySQL connection pool is stop, reutrning a nullptr!");
        return nullptr;
    }
    // 若没有停止，则取出一个连接返回
    conn = std::move(_pool.front());
    _pool.pop();
    _pool_size--;
    return conn;
}

void MySQLConnectionPool::returnConnection(std::unique_ptr<SqlConnection> con)
{
    std::unique_lock<std::mutex> lock(_mutex);

    if (_b_stop)
    {
        // std::cout << "MySQL connection pool is stop, cannot return any connection!";
        LOG_DB_WARN("MySQL connection pool is stop, cannot return any connection!");
        return;
    }

    _pool.push(std::move(con));
    _pool_size++;
    _cond.notify_one();
}

void MySQLConnectionPool::Close()
{
    _b_stop = true;
    _cond.notify_all();
    // while(!_pool.empty())    //不用在这里清空连接池，因为可能以后还要启动
    // {
    //     _pool.pop();
    // }
}

void MySQLConnectionPool::Start()
{
    std::unique_lock<std::mutex> lock;
    _b_stop = false;
}

MySQLDao::MySQLDao()
{
    // 首先先读取配置
    auto &conf = ConfigMgr::Inst();
    SectionInfo MySQL = conf["MySQL"];
    std::string host = MySQL["Host"]; // 也可以用conf["MySQL"]["Host"]的形式获取
    std::string port = MySQL["Port"];
    std::string username = MySQL["UserName"];
    std::string pwd = MySQL["Pwd"];
    std::string schema = MySQL["Schema"];
    std::string url = host + ":" + port;
    // std::cout << "MySQL url: " << url << std::endl;
    LOG_DB_INFO("MySQL url: {}", url);
    // 初始化连接池
    _pool.reset(new MySQLConnectionPool(url, username, pwd, schema, 10));
}

MySQLDao::~MySQLDao()
{
    _pool->Close();
}

void MySQLDao::test()
{
    try
    {
        auto con = _pool->getConnection();
        auto stmt = con->_con->createStatement();
        auto res = stmt->executeQuery("SELECT shop_id FROM shop");
        while (res->next())
        {
            int id = res->getInt("shop_id");
            std::cout << id << std::endl;
        }
    }
    catch (const sql::SQLException &e)
    {
        // std::cerr << "SQL Exception in test: "
        //           << e.what() << std::endl;
        LOG_DB_ERROR("MySQL Error Occured! Exception is: {}", e.what());
    }
}

void MySQLDao::getShopDatas(json &j)
{
    getShopDatasByName(j, "");
    // // TODO:可以添加输出查询结果的语句
}

void MySQLDao::getBannerUrls(int shop_id, json &j)
{
    std::vector<json> vec;
    try
    {
        auto con = _pool->getConnection();
        auto stmt = con->_con->prepareStatement("SELECT * FROM banner_urls WHERE shop_id=?");
        stmt->setInt(1, shop_id);
        auto data = stmt->executeQuery();
        int count = 1;
        json j2;
        while (data->next())
        {
            std::string img_url = data->getString("img_url");
            std::string t_goto = data->getString("goto");
            j2["id"] = count++;
            j2["img_url"] = img_url;
            j2["goto"] = t_goto;
            vec.push_back(j2);
        }

        // TODO:可以添加输出查询结果的语句
        // 为j添加banner_urls对象
        j["banner_urls"] = vec;
    }
    catch (const std::exception &e)
    {
        LOG_DB_ERROR("MySQL Error Occured! Exception is: {}", e.what());
    }
    // catch (const sql::SQLException &e)
    // {
    //     LOG_DB_ERROR("MySQL Error Occured! Exception is: {}", e.what());
    // }
    //     catch (json::exception &e)
    // {
    //     LOG_DB_ERROR("JSON Error Occured! Exception is: {}", e.what());
    // }
}

void MySQLDao::getShopDatasByName(json &res, const std::string &name)
{
    // TODO:可以添加输出查询结果的语句
    auto con = _pool->getConnection();
    sql::PreparedStatement *stmt;
    if (name.empty())
    {
        stmt = con->_con->prepareStatement("SELECT * FROM shop");
    }
    else    //若name不为空
    {
        stmt = con->_con->prepareStatement("SELECT * FROM shop WHERE name=?");
        stmt->setString(1, name);
    }
    auto data = stmt->executeQuery();
    json j2;
    std::vector<json> shops;
    try
    {
        /* code */
        while (data->next())
        {
            int shop_id = data->getInt("shop_id");
            // std::cout << "shop_id: " << shop_id << std::endl;
            // std::string name = data->getString("name");
            std::string description = data->getString("description");
            j2["id"] = shop_id;
            if (name.empty())
                j2["name"] = data->getString("name");
            else
                j2["name"] = name;
            j2["description"] = description;

            // 查询banner_urls
            auto stmt2 = con->_con->prepareStatement("SELECT * FROM banner_urls WHERE shop_id=?");
            stmt2->setInt(1, shop_id);
            auto data2 = stmt2->executeQuery();
            json j3;
            std::vector<json> vec;
            int count = 1;
            while (data2->next())
            {
                std::string img_url = data2->getString("img_url");
                std::string t_goto = data2->getString("goto");
                j3["id"] = count++;
                j3["img_url"] = img_url;
                j3["goto"] = t_goto;
                vec.push_back(j3);
            }
            j2["banner_urls"] = vec;
            shops.push_back(j2);
        }
        res["shops"] = shops;
    }
    catch (const std::exception &e)
    {
        LOG_DB_ERROR("MySQL Error Occured! Exception is: {}", e.what());
    }
    
}
